/************************************************************************* * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. ************************************************************************/ package com.eucalyptus.tokens.oidc; import java.io.IOException; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; import java.security.spec.RSAPublicKeySpec; import java.util.concurrent.ConcurrentMap; import org.apache.log4j.Logger; import org.apache.xml.security.algorithms.implementations.SignatureECDSA; import com.eucalyptus.bootstrap.ServiceJarDiscovery; import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; import javaslang.control.Option; /** * */ public interface JsonWebSignatureAlgorithm { String name( ); String getJcaSignatureAlgorithm( ); Option<String> getJcaSignatureProvider( ); Option<AlgorithmParameterSpec> getJcaSignatureAlgorithmParameterSpec( ); Class<? extends JsonWebKey> keyType( ); <K extends JsonWebKey> PublicKey publicKey( K key ) throws GeneralSecurityException; boolean matches( JsonWebKey key, X509Certificate keyCertificate ) throws GeneralSecurityException; byte[] signature( byte[] signature ) throws GeneralSecurityException; static Option<JsonWebSignatureAlgorithm> lookup( final String algorithm ) { return Option.of( JsonWebSignatureAlgorithmRegistry.algorithmMap.get( algorithm ) ); } @SuppressWarnings( "WeakerAccess" ) abstract class JsonWebSignatureAlgorithmSupport implements JsonWebSignatureAlgorithm { private final String name; private final String jcaSignatureAlgorithm; protected JsonWebSignatureAlgorithmSupport( final String name, final String jcaSignatureAlgorithm ) { this.name = name; this.jcaSignatureAlgorithm = jcaSignatureAlgorithm; } public String name( ) { return name; } @Override public byte[] signature( final byte[] signature ) throws GeneralSecurityException { return signature; } @Override public Option<AlgorithmParameterSpec> getJcaSignatureAlgorithmParameterSpec() { return Option.none( ); } @Override public String getJcaSignatureAlgorithm( ) { return jcaSignatureAlgorithm; } @Override public Option<String> getJcaSignatureProvider() { return Option.none( ); } protected <T extends JsonWebKey> T key( final JsonWebKey key, final Class<T> keyType ) throws GeneralSecurityException { if ( !keyType.isInstance( key ) ) { throw new GeneralSecurityException( "Incorrect key type: " + key.getKty( ) + " for " + name( ) ); } return keyType.cast( key ); } } @SuppressWarnings( "WeakerAccess" ) abstract class EsJsonWebSignatureAlgorithmSupport extends JsonWebSignatureAlgorithmSupport { private final String expectedCurve; private final String jcaCurve; protected EsJsonWebSignatureAlgorithmSupport( final String name, final String jcaSignatureAlgorithm, final String expectedCurve, final String jcaCurve ) { super( name, jcaSignatureAlgorithm ); this.expectedCurve = expectedCurve; this.jcaCurve = jcaCurve; } @Override public Class<? extends JsonWebKey> keyType( ) { return EcJsonWebKey.class; } public <K extends JsonWebKey> PublicKey publicKey( final K key ) throws GeneralSecurityException { final EcJsonWebKey webKey = key( key, EcJsonWebKey.class ); if ( !name( ).equals( webKey.getAlg( ) ) ) { throw new GeneralSecurityException( "Invalid key algorithm " + webKey.getAlg( ) + " for " + name( ) ); } if ( !expectedCurve.equals( webKey.getCrv( ) ) ) { throw new GeneralSecurityException( "Invalid curve " + webKey.getCrv( ) + " for " + name( ) ); } final BigInteger x = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getX( ) ) ); final BigInteger y = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getY( ) ) ); final AlgorithmParameters parameters = AlgorithmParameters.getInstance( "EC" ); parameters.init( new ECGenParameterSpec( jcaCurve ) ); final ECParameterSpec ecParameters = parameters.getParameterSpec( ECParameterSpec.class ); return KeyFactory.getInstance( "EC" ).generatePublic( new ECPublicKeySpec( new ECPoint( x, y ), ecParameters ) ); } @Override public boolean matches( final JsonWebKey key, final X509Certificate keyCertificate ) throws GeneralSecurityException { final EcJsonWebKey webKey = key( key, EcJsonWebKey.class ); final PublicKey certPublicKey = keyCertificate.getPublicKey( ); if ( (certPublicKey instanceof ECPublicKey) ) { final ECPublicKey ecCertPublicKey = (ECPublicKey) certPublicKey; final BigInteger x = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getX( ) ) ); final BigInteger y = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getY( ) ) ); return ecCertPublicKey.getW( ).equals( new ECPoint( x, y ) ); } return false; } @Override public byte[] signature( final byte[] signature ) throws GeneralSecurityException { try { return SignatureECDSA.convertXMLDSIGtoASN1( signature ); } catch ( final IOException e ) { throw new GeneralSecurityException( "ECDSA ASN.1 conversion failed", e ); } } } @SuppressWarnings( "WeakerAccess" ) abstract class RsaJsonWebSignatureAlgorithmSupport extends JsonWebSignatureAlgorithmSupport { protected RsaJsonWebSignatureAlgorithmSupport( final String name, final String jcaSignatureAlgorithm ) { super( name, jcaSignatureAlgorithm ); } @Override public Class<? extends JsonWebKey> keyType( ) { return RsaJsonWebKey.class; } @Override public boolean matches( final JsonWebKey key, final X509Certificate keyCertificate ) throws GeneralSecurityException { final RsaJsonWebKey webKey = key( key, RsaJsonWebKey.class ); final PublicKey certPublicKey = keyCertificate.getPublicKey( ); if ( (certPublicKey instanceof RSAPublicKey ) ) { final RSAPublicKey rsaCertPublicKey = (RSAPublicKey) certPublicKey; final BigInteger modulus = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getN( ) ) ); final BigInteger publicExponent = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getE( ) ) ); return rsaCertPublicKey.getPublicExponent( ).equals( publicExponent ) && rsaCertPublicKey.getModulus( ).equals( modulus ); } return false; } public <K extends JsonWebKey> PublicKey publicKey( final K key ) throws GeneralSecurityException { final RsaJsonWebKey webKey = key( key, RsaJsonWebKey.class ); final BigInteger modulus = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getN( ) ) ); final BigInteger publicExponent = new BigInteger( 1, BaseEncoding.base64Url( ).decode( webKey.getE( ) ) ); final int keyBits = modulus.bitLength( ); if ( keyBits < 2048 ) { throw new GeneralSecurityException( "RSA key too small " +keyBits+ "bits" ); } return KeyFactory.getInstance( "RSA" ).generatePublic( new RSAPublicKeySpec( modulus, publicExponent ) ); } } @SuppressWarnings( "WeakerAccess" ) abstract class PsJsonWebSignatureAlgorithmSupport extends RsaJsonWebSignatureAlgorithmSupport { private final String psDigest; private final MGF1ParameterSpec mgf1ParameterSpec; private final int saltLen; protected PsJsonWebSignatureAlgorithmSupport( final String name, final String jcaSignatureAlgorithm, final String psDigest, final MGF1ParameterSpec mgf1ParameterSpec, final int saltLen ) { super( name, jcaSignatureAlgorithm ); this.psDigest = psDigest; this.mgf1ParameterSpec = mgf1ParameterSpec; this.saltLen = saltLen; } @Override public Option<AlgorithmParameterSpec> getJcaSignatureAlgorithmParameterSpec( ) { return Option.some( new PSSParameterSpec( psDigest, "MGF1", mgf1ParameterSpec, saltLen, 1 ) ); } } class Es256JsonWebSignatureAlgorithm extends EsJsonWebSignatureAlgorithmSupport { public Es256JsonWebSignatureAlgorithm( ) { super( "ES256", "SHA256withECDSA", "P-256", "secp256r1" ); } } class Es384JsonWebSignatureAlgorithm extends EsJsonWebSignatureAlgorithmSupport { public Es384JsonWebSignatureAlgorithm( ) { super( "ES384", "SHA384withECDSA", "P-384", "secp384r1" ); } } class Es512JsonWebSignatureAlgorithm extends EsJsonWebSignatureAlgorithmSupport { public Es512JsonWebSignatureAlgorithm( ) { super( "ES512", "SHA512withECDSA", "P-521", "secp521r1" ); // 521 is not a typo } } class Ps256JsonWebSignatureAlgorithm extends PsJsonWebSignatureAlgorithmSupport { public Ps256JsonWebSignatureAlgorithm( ) { super( "PS256", "SHA256withRSAandMGF1", "SHA256", MGF1ParameterSpec.SHA256, 32 ); } } class Ps384JsonWebSignatureAlgorithm extends PsJsonWebSignatureAlgorithmSupport { public Ps384JsonWebSignatureAlgorithm( ) { super( "PS384", "SHA384withRSAandMGF1", "SHA384", MGF1ParameterSpec.SHA384, 48 ); } } class Ps512JsonWebSignatureAlgorithm extends PsJsonWebSignatureAlgorithmSupport { public Ps512JsonWebSignatureAlgorithm( ) { super( "PS512", "SHA512withRSAandMGF1", "SHA512", MGF1ParameterSpec.SHA512, 64 ); } } class Rs256JsonWebSignatureAlgorithm extends RsaJsonWebSignatureAlgorithmSupport { public Rs256JsonWebSignatureAlgorithm( ) { super( "RS256", "SHA256withRSA" ); } } class Rs384JsonWebSignatureAlgorithm extends RsaJsonWebSignatureAlgorithmSupport { public Rs384JsonWebSignatureAlgorithm( ) { super( "RS384", "SHA384withRSA" ); } } class Rs512JsonWebSignatureAlgorithm extends RsaJsonWebSignatureAlgorithmSupport { public Rs512JsonWebSignatureAlgorithm( ) { super( "RS512", "SHA512withRSA" ); } } class JsonWebSignatureAlgorithmRegistry { private static final ConcurrentMap<String,JsonWebSignatureAlgorithm> algorithmMap = Maps.newConcurrentMap( ); public static void register( final JsonWebSignatureAlgorithm algorithm ) { algorithmMap.putIfAbsent( algorithm.name( ), algorithm ); } } @SuppressWarnings( "unused" ) class JsonWebSignatureAlgorithmDiscovery extends ServiceJarDiscovery { private static final Logger logger = Logger.getLogger( JsonWebSignatureAlgorithmDiscovery.class ); @Override public Double getPriority() { return 1.0d; } @Override public boolean processClass( final Class candidate ) { if ( JsonWebSignatureAlgorithm.class.isAssignableFrom( candidate ) && !Modifier.isAbstract( candidate.getModifiers( ) ) && Modifier.isPublic( candidate.getModifiers( ) ) ) { try { final JsonWebSignatureAlgorithm instance = (JsonWebSignatureAlgorithm) candidate.newInstance( ); JsonWebSignatureAlgorithmRegistry.register( instance ); } catch ( InstantiationException | IllegalAccessException e ) { logger.error( "Error registering JSON Web Signature algorithm class: " + candidate, e ); } return true; } return false; } } }